{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "id": "hkZw98BK0AKv"
   },
   "outputs": [],
   "source": [
    "# Copyright 2024 Google LLC\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6bb894cc4c54"
   },
   "source": [
    "# Multimodal Prompting with Gemini: Working with Videos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AGhNH-y9z5EZ"
   },
   "source": [
    "<table align=\"left\">\n",
    "<td style=\"text-align: center\">\n",
    "<a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/applied-ai-engineering-samples/blob/main/genai-on-vertex-ai/gemini/prompting_recipes/multimodal/multimodal_prompting_video.ipynb\">\n",
    "<img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"Google Colaboratory logo\"><br> Run in Colab\n",
    "</a>\n",
    "</td>\n",
    "      <td style=\"text-align: center\">\n",
    "    <a href=\"https://console.cloud.google.com/vertex-ai/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fapplied-ai-engineering-samples%2Fmain%2Fgenai-on-vertex-ai%2Fgemini%2Fprompting_recipes%2Fmultimodal%2Fmultimodal_prompting_video.ipynb\">\n",
    "      <img width=\"32px\" src=\"https://lh3.googleusercontent.com/JmcxdQi-qOpctIvWKgPtrzZdJJK-J3sWE1RsfjZNwshCFgE_9fULcNpuXYTilIR2hjwN\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
    "    </a>\n",
    "  </td>\n",
    "<td style=\"text-align: center\">\n",
    "<a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/applied-ai-engineering-samples/main/genai-on-vertex-ai/gemini/prompting_recipes/multimodal/multimodal_prompting_video.ipynb\">\n",
    "<img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\"><br> Open in Vertex AI Workbench\n",
    "</a>\n",
    "</td>    \n",
    "<td style=\"text-align: center\">\n",
    "<a href=\"https://github.com/GoogleCloudPlatform/applied-ai-engineering-samples/blob/main/genai-on-vertex-ai/gemini/prompting_recipes/multimodal/multimodal_prompting_video.ipynb\">\n",
    "<img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\"><br> View on GitHub\n",
    "</a>\n",
    "</td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dqS4jWxr0Eyz"
   },
   "source": [
    "| | |\n",
    "|-|-|\n",
    "| Author(s) | [Michael Chertushkin](https://github.com/misha-chertushkin) |\n",
    "| Reviewer(s) | [Rajesh Thallam](https://github.com/rthallam), [Skander Hannachi](https://github.com/skanderhn)  |\n",
    "| Last updated | 2024-09-16 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4RS2kzIzdp-u"
   },
   "source": [
    "# Overview\n",
    "\n",
    "---\n",
    "\n",
    "Gemini models supports adding image, audio, video, and PDF files in text or chat prompts for a text or code response. Gemini 2.0 Flash supports up to 1 Million input tokens with up to 1 hours length of video per prompt. Gemini can analyze the audio embedded within a video as well. You can add videos to Gemini requests to perform [video analysis tasks](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/video-understanding) such as video summarization, video chapterization (or localization), key event detection, scene analysis, captioning and transcription and more. \n",
    "\n",
    "---\n",
    "\n",
    "In this notebook we cover prompting recipes and strategies for working with Gemini on videos and show some examples on the way. This notebook is organized as follows:\n",
    "\n",
    "- Video Understanding\n",
    "- Key event detection\n",
    "- Using System instruction\n",
    "- Analyzing videos with step-by-step reasoning\n",
    "- Generating structured output\n",
    "- Using context caching for repeated queries\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "acd63312c2f4"
   },
   "source": [
    "# Getting Started\n",
    "\n",
    "The following steps are necessary to run this notebook, no matter what notebook environment you're using.\n",
    "\n",
    "If you're entirely new to Google Cloud, [get started here](https://cloud.google.com/docs/get-started)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "13e6fde93ea3"
   },
   "source": [
    "## Google Cloud Project Setup\n",
    "\n",
    "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n",
    "1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
    "1. [Enable the Service Usage API](https://console.cloud.google.com/apis/library/serviceusage.googleapis.com)\n",
    "1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
    "1. [Enable the Cloud Storage API](https://console.cloud.google.com/flows/enableapi?apiid=storage.googleapis.com)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d9b5ae4999b9"
   },
   "source": [
    "## Google Cloud Permissions\n",
    "\n",
    "**To run the complete Notebook, including the optional section, you will need to have the [Owner role](https://cloud.google.com/iam/docs/understanding-roles) for your project.**\n",
    "\n",
    "If you want to skip the optional section, you need at least the following [roles](https://cloud.google.com/iam/docs/granting-changing-revoking-access):\n",
    "* **`roles/serviceusage.serviceUsageAdmin`** to enable APIs\n",
    "* **`roles/iam.serviceAccountAdmin`** to modify service agent permissions\n",
    "* **`roles/aiplatform.user`** to use AI Platform components\n",
    "* **`roles/storage.objectAdmin`** to modify and delete GCS buckets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2b203ddf1cdc"
   },
   "source": [
    "## Install Vertex AI SDK for Python and other dependencies (If Needed)\n",
    "\n",
    "The list `packages` contains tuples of package import names and install names. If the import name is not found then the install name is used to install quitely for the current user.## Install Vertex AI SDK for Python and other dependencies (If Needed)\n",
    "\n",
    "The list `packages` contains tuples of package import names and install names. If the import name is not found then the install name is used to install quitely for the current user."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "514241a24fa4"
   },
   "outputs": [],
   "source": [
    "! pip install google-cloud-aiplatform --upgrade --quiet --user"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5b187dc025e0"
   },
   "source": [
    "## Restart Runtime\n",
    "\n",
    "To use the newly installed packages in this Jupyter runtime, you must restart the runtime. You can do this by running the cell below, which will restart the current kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "8b08062f2883"
   },
   "outputs": [],
   "source": [
    "# Restart kernel after installs so that your environment can access the new packages\n",
    "import IPython\n",
    "\n",
    "app = IPython.Application.instance()\n",
    "app.kernel.do_shutdown(True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6791e371ace9"
   },
   "source": [
    "## Authenticate\n",
    "\n",
    "If you're using Colab, run the code in the next cell. Follow the popups and authenticate with an account that has access to your Google Cloud [project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects).\n",
    "\n",
    "If you're running this notebook somewhere besides Colab, make sure your environment has the right Google Cloud access. If that's a new concept to you, consider looking into [Application Default Credentials for your local environment](https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev) and [initializing the Google Cloud CLI](https://cloud.google.com/docs/authentication/gcloud). In many cases, running `gcloud auth application-default login` in a shell on the machine running the notebook kernel is sufficient.\n",
    "\n",
    "More authentication options are discussed [here](https://cloud.google.com/docs/authentication)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "id": "51cabca59af0"
   },
   "outputs": [],
   "source": [
    "# Colab authentication.\n",
    "import sys\n",
    "\n",
    "if \"google.colab\" in sys.modules:\n",
    "    from google.colab import auth\n",
    "\n",
    "    auth.authenticate_user()\n",
    "    print(\"Authenticated\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a9e68b09a55c"
   },
   "source": [
    "## Set Google Cloud project information and Initialize Vertex AI SDK\n",
    "\n",
    "To get started using Vertex AI, you must have an existing Google Cloud project and [enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
    "\n",
    "Learn more about [setting up a project and a development environment](https://cloud.google.com/vertex-ai/docs/start/cloud-environment).\n",
    "\n",
    "Make sure to change `PROJECT_ID` in the next cell. You can leave the values for `REGION` unless you have a specific reason to change them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "5256307afcd5",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import vertexai\n",
    "\n",
    "PROJECT_ID = \"[your-project-id]\"  # @param {type:\"string\"}\n",
    "REGION = \"us-central1\"  # @param {type:\"string\"}\n",
    "\n",
    "vertexai.init(project=PROJECT_ID, location=REGION)\n",
    "print(\"Vertex AI SDK initialized.\")\n",
    "print(f\"Vertex AI SDK version = {vertexai.__version__}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "89c6c77513de"
   },
   "source": [
    "## Import Libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "2042cf8dce9f",
    "tags": []
   },
   "outputs": [],
   "source": [
    "from vertexai.generative_models import (GenerationConfig, GenerativeModel,\n",
    "                                        HarmBlockThreshold, HarmCategory, Part)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d9e80e805ceb"
   },
   "source": [
    "## Define Utility functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "c36297c5650f",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import http.client\n",
    "import textwrap\n",
    "import typing\n",
    "import urllib.request\n",
    "\n",
    "from google.cloud import storage\n",
    "from IPython import display\n",
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "\n",
    "InteractiveShell.ast_node_interactivity = \"all\"\n",
    "\n",
    "\n",
    "def wrap(string, max_width=80):\n",
    "    return textwrap.fill(string, max_width)\n",
    "\n",
    "\n",
    "def get_bytes_from_url(url: str) -> bytes:\n",
    "    with urllib.request.urlopen(url) as response:\n",
    "        response = typing.cast(http.client.HTTPResponse, response)\n",
    "        bytes = response.read()\n",
    "    return bytes\n",
    "\n",
    "\n",
    "def get_bytes_from_gcs(gcs_path: str):\n",
    "    bucket_name = gcs_path.split(\"/\")[2]\n",
    "    object_prefix = \"/\".join(gcs_path.split(\"/\")[3:])\n",
    "    storage_client = storage.Client()\n",
    "    bucket = storage_client.bucket(bucket_name)\n",
    "    blob = bucket.get_blob(object_prefix)\n",
    "    return blob.download_as_bytes()\n",
    "\n",
    "\n",
    "def display_image(image_url: str, width: int = 300, height: int = 200):\n",
    "    if image_url.startswith(\"gs://\"):\n",
    "        image_bytes = get_bytes_from_gcs(image_url)\n",
    "    else:\n",
    "        image_bytes = get_bytes_from_url(image_url)\n",
    "    display.display(display.Image(data=image_bytes, width=width, height=height))\n",
    "\n",
    "\n",
    "def display_video(video_url: str, width: int = 300, height: int = 200):\n",
    "    if video_url.startswith(\"gs://\"):\n",
    "        video_bytes = get_bytes_from_gcs(video_url)\n",
    "    else:\n",
    "        video_bytes = get_bytes_from_url(video_url)\n",
    "    display.display(\n",
    "        display.Video(\n",
    "            data=video_bytes,\n",
    "            width=width,\n",
    "            height=height,\n",
    "            embed=True,\n",
    "            mimetype=\"video/mp4\",\n",
    "        )\n",
    "    )\n",
    "\n",
    "def display_audio(audio_url: str, width: int = 300, height: int = 200):\n",
    "    if audio_url.startswith(\"gs://\"):\n",
    "        audio_bytes = get_bytes_from_gcs(audio_url)\n",
    "    else:\n",
    "        audio_bytes = get_bytes_from_url(audio_url)\n",
    "    display.display(display.Audio(data=audio_bytes, embed=True))\n",
    "\n",
    "\n",
    "def print_prompt(contents: list[str | Part]):\n",
    "    for content in contents:\n",
    "        if isinstance(content, Part):\n",
    "            if content.mime_type.startswith(\"image\"):\n",
    "                display_image(image_url=content.file_data.file_uri)\n",
    "            elif content.mime_type.startswith(\"video\"):\n",
    "                display_video(video_url=content.file_data.file_uri)\n",
    "            elif content.mime_type.startswith(\"audio\"):\n",
    "                display_audio(audio_url=content.file_data.file_uri)\n",
    "            else:\n",
    "                print(content)\n",
    "        else:\n",
    "            print(content)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "4mmDittp23Gp"
   },
   "source": [
    "## Initialize Gemini"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "xOwys5I724od",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Gemini Config\n",
    "GENERATION_CONFIG = {\n",
    "    \"max_output_tokens\": 8192,\n",
    "    \"temperature\": 0.1,\n",
    "    \"top_p\": 0.95,\n",
    "}\n",
    "\n",
    "SAFETY_CONFIG = {\n",
    "    HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,\n",
    "    HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,\n",
    "    HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,\n",
    "    HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,\n",
    "}\n",
    "\n",
    "gemini = GenerativeModel(model_name=\"gemini-2.0-flash-001\")\n",
    "videos_path_prefix = (\n",
    "    \"gs://public-aaie-genai-samples/gemini/prompting_recipes/multimodal/videos\"\n",
    ")\n",
    "\n",
    "\n",
    "def generate(\n",
    "    model,\n",
    "    contents,\n",
    "    safety_settings=SAFETY_CONFIG,\n",
    "    generation_config=GENERATION_CONFIG,\n",
    "    as_markdown=False,\n",
    "):\n",
    "    responses = model.generate_content(\n",
    "        contents=contents,\n",
    "        generation_config=generation_config,\n",
    "        safety_settings=safety_settings,\n",
    "        stream=False,\n",
    "    )\n",
    "    if isinstance(responses, list):\n",
    "        for response in responses:\n",
    "            if as_markdown:\n",
    "                display.display(display.Markdown(response.text))\n",
    "            else:\n",
    "                print(wrap(response.text), end=\"\")\n",
    "    else:\n",
    "        if as_markdown:\n",
    "            display.display(display.Markdown(responses.text))\n",
    "        else:\n",
    "            print(wrap(responses.text), end=\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "w1ZnbbNo5DHS",
    "tags": []
   },
   "outputs": [],
   "source": [
    "display_video(\n",
    "    video_url=\"gs://public-aaie-genai-samples/gemini/prompting_recipes/multimodal/videos/video_1.mp4\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "t8s94ynm1vGt"
   },
   "source": [
    "# Prompt #1. Video Understanding\n",
    "\n",
    "This task requires the input to be presented in two different modalities: text and video. The example of the API call is below, however this is non-optimal prompt and we can make it better."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "id": "5563489ed4f4",
    "tags": []
   },
   "outputs": [],
   "source": [
    "video_path = f\"{videos_path_prefix}/video_1.mp4\"\n",
    "video_content = Part.from_uri(uri=video_path, mime_type=\"video/mp4\")\n",
    "prompt = \"\"\"Provide a description of the video. The description should also \n",
    "contain anything important which people say in the video.\"\"\"\n",
    "\n",
    "contents = [video_content, prompt]\n",
    "# print_prompt(contents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "id": "2bFaqufh5xIN",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here is a description of the video:  The video shows a person tossing a pink\n",
      "collapsible cup in the air and catching it. The background is a white curtain.\n",
      "The person's arm and hand are visible. The cup is the main focus of the video.\n",
      "The video is shot in a bright, minimalist style. There is no audio in the video."
     ]
    }
   ],
   "source": [
    "generate(gemini, contents)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "_QJnFXeqAvaT"
   },
   "source": [
    "As we see the model correctly picked what happens there, but it did not provide much details. Let's modify the prompt."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ecIw8YDWISQf"
   },
   "source": [
    "### Video Understanding. Advanced Prompt\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "MWnDgTHzAtqg",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Here's a detailed analysis of the video:\n",
       "\n",
       "*   **00:00** A hand throws a pink collapsible cup into the air against a white curtain backdrop.\n",
       "*   **00:01** The hand catches the cup.\n",
       "*   **00:02** The hand throws the cup into the air again, this time in its collapsed form.\n",
       "*   **00:03** The hand catches the cup in its expanded form.\n",
       "*   **00:04** The hand shakes the cup.\n",
       "*   **00:05** The hand holds the cup still.\n",
       "*   **00:06** The hand moves the cup around.\n",
       "*   **00:07** The hand throws the cup into the air again.\n",
       "*   **00:08** The hand holds the cup still.\n",
       "*   **00:09** The hand shakes the cup.\n",
       "*   **00:10** The hand holds the cup still.\n",
       "\n",
       "The central theme of the video is showcasing a pink collapsible cup. The hand interacts with the cup by throwing it in the air, catching it, shaking it, and holding it still. The white curtain backdrop provides a clean and simple background, drawing attention to the cup and the hand's interaction with it.\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "prompt = \"\"\"You are an expert video analyzer. You task is to analyze the video \n",
    "and produce the detailed description about what happens on the video.\n",
    "\n",
    "Key Points:\n",
    "- Use timestamps (in MM:SS format) to output key events from the video.\n",
    "- Add information about what happens at each timestamp.\n",
    "- Add information about entities in the video and capture the relationship between them.\n",
    "- Highlight the central theme or focus of the video.\n",
    "\n",
    "Remember:\n",
    "- Try to recover hidden meaning from the scene. For example, some hidden humor \n",
    "  or some hidden context.\n",
    "\"\"\"\n",
    "\n",
    "contents = [video_content, prompt]\n",
    "generate(gemini, contents, as_markdown=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "QAzxT1LNB-Sj"
   },
   "source": [
    "The response with the updated prompt captures much more details. Although this prompt is rather generic and can be used for other videos, let's add specifics to the prompt. For example, if we want to capture at which time certain event happened."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NACDJqhQIYK3"
   },
   "source": [
    "# Prompt #2. Video Understanding: Key events detection\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "id": "JE5P7Hf-4rhl",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Here's a detailed analysis of the video:\n",
       "\n",
       "*   **00:00** A hand throws a pink collapsible cup into the air against a white curtain backdrop.\n",
       "*   **00:01** The hand catches the cup.\n",
       "*   **00:02** The hand throws the cup again.\n",
       "*   **00:03** The hand catches the cup again.\n",
       "*   **00:04** The hand shakes the cup.\n",
       "*   **00:07** The hand throws the cup again.\n",
       "*   **00:08** The hand catches the cup again.\n",
       "*   **00:09** The hand shakes the cup.\n",
       "\n",
       "The central theme of the video is a person playing with a pink collapsible cup, repeatedly throwing it into the air and catching it.\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "prompt = \"\"\"You are an expert video analyzer. You task is to analyze the video \n",
    "and produce the detailed description about what happens on the video.\n",
    "\n",
    "Key Points:\n",
    "- Use timestamps (in MM:SS format) to output key events from the video.\n",
    "- Add information about what happens at each timestamp.\n",
    "- Add information about entities in the video and capture the relationship between them.\n",
    "- Highlight the central theme or focus of the video.\n",
    "\n",
    "Remember:\n",
    "- Try to recover hidden meaning from the scene. For example, some hidden humor \n",
    "  or some hidden context.\n",
    "\n",
    "At which moment the cup was thrown for the second time?\n",
    "\"\"\"\n",
    "\n",
    "contents = [video_content, prompt]\n",
    "generate(gemini, contents, as_markdown=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OxLf3GkLJS6u"
   },
   "source": [
    "# Prompt #3. Video Understanding: Using System instruction\n",
    "\n",
    "System Instruction (SI) is an effective way to steer Gemini's behavior and shape \n",
    "how the model responds to your prompt. SI can be used to describe model behavior \n",
    "such as persona, goal, tasks to perform, output format / tone / style, any constraints etc. \n",
    "\n",
    "SI behaves more \"sticky\" (or consistent) during multi-turn behavior. For example, \n",
    "if you want to achieve a behavior that the model will consistently follow, then \n",
    "system instruction is the best way to put this instruction.\n",
    "\n",
    "In this example, we will move the task rules to system instruction and the \n",
    "question on a specific event in the user prompt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "id": "qPZurMKjJpqG",
    "tags": []
   },
   "outputs": [],
   "source": [
    "system_prompt = \"\"\"You are an expert video analyzer. You task is to analyze the video \n",
    "and produce the detailed description about what happens on the video.\n",
    "\n",
    "Key Points:\n",
    "- Use timestamps (in MM:SS format) to output key events from the video.\n",
    "- Add information about what happens at each timestamp.\n",
    "- Add information about entities in the video and capture the relationship between them.\n",
    "- Highlight the central theme or focus of the video.\n",
    "\n",
    "Remember:\n",
    "- Try to recover hidden meaning from the scene. For example, some hidden humor \n",
    "  or some hidden context.\n",
    "\"\"\"\n",
    "\n",
    "prompt = \"At which moment the cup was thrown for the second time?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "id": "2fbb0fd520d1",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "[00:07] The cup was thrown for the second time.\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "gemini_si = GenerativeModel(\n",
    "    model_name=\"gemini-2.0-flash-001\", system_instruction=system_prompt\n",
    ")\n",
    "\n",
    "contents = [video_content, prompt]\n",
    "generate(gemini_si, contents, as_markdown=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OzejnMTf7yxb"
   },
   "source": [
    "# Prompt #4. Video Understanding: Step-by-step reasoning\n",
    "\n",
    "We see that actually a mistake happened in analyzing the video. The model does not show all the timestamps where the cup is thrown. Let's fix it with \"step-by-step reasoning\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "id": "iZajqS827x8I",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Here's a breakdown of the video:\n",
       "\n",
       "The central theme of the video revolves around a person playfully tossing and catching a pink, collapsible cup. The background is a simple white curtain, keeping the focus entirely on the cup and the hand interacting with it.\n",
       "\n",
       "Here's a step-by-step analysis with timestamps:\n",
       "\n",
       "*   **00:00** The person throws the pink cup into the air.\n",
       "*   **00:01** The person catches the pink cup.\n",
       "*   **00:02** The person throws the pink cup into the air again.\n",
       "*   **00:03** The person catches the pink cup.\n",
       "*   **00:07** The person throws the pink cup into the air for the third time.\n",
       "\n",
       "The cup is thrown for the second time at **00:02**."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "step_by_step_prompt = \"\"\"Describe the video. Analyze the video step-by-step. \n",
    "Output all times when the cup is thrown with timestamps. \n",
    "After that output the timestamp, when the cup is thrown for the second time.\n",
    "\"\"\"\n",
    "\n",
    "contents = [video_content, step_by_step_prompt]\n",
    "generate(gemini_si, contents, as_markdown=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "86NmGY798oMC"
   },
   "source": [
    "# Prompt #5. Video Understanding: Get structured outputs\n",
    "\n",
    "Gemini models can generate structured outputs such as JSON, providing a blueprint for the model's output. This feature is also referred to as [controlled generation](https://developers.googleblog.com/en/mastering-controlled-generation-with-gemini-15-schema-adherence/). \n",
    "\n",
    "In this example, we demonstrate Gemini to return structured output (JSON) from a video analysis. One of the ways to achieve better understanding of video (or any multimodal) content is to prompt the model to explain its \"reasoning\" about the response. This has proven to be very effective method, however it can increase the latency. \n",
    "\n",
    "[Vertex AI Gemini API](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output) makes it easy to return JSON output by configuring response MIME type as `application/json`. Optionally, you can also configure `response_schema` with the JSON schema for the model to generate output as per the schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "id": "d8d4675dc101",
    "tags": []
   },
   "outputs": [],
   "source": [
    "response_schema = {\n",
    "    \"type\": \"ARRAY\",\n",
    "    \"items\": {\n",
    "        \"type\": \"OBJECT\",\n",
    "        \"properties\": {\n",
    "            \"harmfulness_reasoning\": {\n",
    "                \"type\": \"STRING\",\n",
    "                \"description\": \"Step-by-step detailed reasoning about how harmful is the video\",\n",
    "            },\n",
    "            \"harmfulness_score\": {\n",
    "                \"type\": \"INTEGER\",\n",
    "                \"description\": \"Number between 0 and 5 indicating how harmful is the video\",\n",
    "            },\n",
    "        },\n",
    "        \"required\": [\"harmfulness_reasoning\", \"harmfulness_score\"],\n",
    "    },\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "id": "87b34d3255b7",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[   {     \"harmfulness_reasoning\": \"The video features a hand tossing and\n",
      "catching a pink cup. There is no indication of any harmful or dangerous content,\n",
      "nor does it contain any themes or visuals that would be considered inappropriate\n",
      "for children. The scene is simple and does not present any risk of promoting\n",
      "negative behaviors.\",     \"harmfulness_score\": 0   } ]"
     ]
    }
   ],
   "source": [
    "structured_prompt = \"\"\"You are an expert video analyzer. You task is to analyze the video \n",
    "and produce a harmfulness score - how harmful this video can be for kids.\"\"\"\n",
    "\n",
    "contents = [video_content, structured_prompt]\n",
    "\n",
    "generate(\n",
    "    gemini,\n",
    "    contents,\n",
    "    generation_config=GenerationConfig(\n",
    "        response_mime_type=\"application/json\", response_schema=response_schema\n",
    "    ),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "EJf-Iq8TOxKo"
   },
   "source": [
    "The model returned the correct score for the video by asking the model to output \"reasoning\" along with the score. Adding \"reasoning\" field before the \"score\" gives a consistent and correct score. The intuition is  that LLM can generate \"reasoning\" first and rely on the thoughts to properly produce the score."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "33SnNRcvLg73"
   },
   "source": [
    "# Prompt #6. Video Understanding: Context Caching\n",
    "\n",
    "[Context caching](https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview?hl=en) is a method to reduce the cost of requests that contain repeated content with high input token count. It can potentially reduce the latency at the cost of storing the objects in the cache. The user can specify cache expiration time for which the object is saved in cache.\n",
    "\n",
    "Context caching helps a lot when we want:\n",
    "- to repeatedly ask questions about the long video\n",
    "- to reduce costs and save latency"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "id": "b74299377f9f",
    "tags": []
   },
   "outputs": [],
   "source": [
    "long_video_path = f\"{videos_path_prefix}/long_video_1.mp4\"\n",
    "long_video_content = Part.from_uri(uri=long_video_path, mime_type=\"video/mp4\")\n",
    "\n",
    "prompt = \"\"\"Describe what happens in the beginning, in the middle and in the \n",
    "end of the video. Also, list the name of the main character and any problems \n",
    "they face.\"\"\"\n",
    "\n",
    "contents = [long_video_content, prompt]\n",
    "# print_prompt(contents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "id": "IzJU9CoiMoBj",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here's a breakdown of the video:  **Beginning (0:00 - 1:25):**  *   The video\n",
      "opens with the title card for \"Sherlock Jr.\" starring Buster Keaton, presented\n",
      "by Joseph M. Schenck. *   Credits for the story, photography, art direction, and\n",
      "electrician are shown. *   Copyright information for 1924 is displayed. *   A\n",
      "proverb appears: \"Don't try to do two things at once and expect to do justice to\n",
      "both.\" *   The opening narration sets the scene: a boy working as a moving\n",
      "picture operator in a small-town theater is also studying to be a detective.\n",
      "**Middle (1:26 - 38:59):**  *   The video shows the main character, a young man\n",
      "with a mustache, reading a book titled \"How-To-Be-A-Detective\" in an empty\n",
      "theater. *   His boss tells him to clean the theater instead of reading\n",
      "detective books. *   The young man is shown sweeping the theater and then\n",
      "walking to a confectionery store. *   He sees a girl in the store and wants to\n",
      "buy her chocolates, but he doesn't have enough money. *   The girl's father\n",
      "hires a man to help him. *   The girl is seen with a dog, and a man steals her\n",
      "ring. *   The girl's father is seen with the man he hired, and he discovers that\n",
      "his watch has been stolen. *   The young man is called to investigate the theft.\n",
      "*   The young man searches everyone, but the watch is not found. *   The young\n",
      "man is told to leave the house and never come back. *   The young man returns to\n",
      "the theater and starts the movie. *   The movie is \"Hearts and Pearls\" and the\n",
      "young man falls asleep. *   The young man dreams that he is in the movie. *\n",
      "The young man is seen in the movie, and he is trying to steal the pearls. *\n",
      "The young man is seen in the movie, and he is trying to escape. *   The young\n",
      "man is seen in the movie, and he is being chased by the police. *   The young\n",
      "man is seen in the movie, and he is trying to get away on a motorcycle. *   The\n",
      "young man is seen in the movie, and he is trying to get away in a car. *   The\n",
      "young man is seen in the movie, and he is trying to get away in a boat. *   The\n",
      "young man is seen in the movie, and he is swimming away.  **End (39:00 -\n",
      "44:06):**  *   The young man wakes up and is back in the projection booth. *\n",
      "The girl comes to the projection booth and tells him that her father made a\n",
      "mistake. *   The girl shows the young man the pawn ticket for the watch. *   The\n",
      "young man is seen in the movie, and he is now a detective. *   The detective is\n",
      "seen with his assistant, Gillette. *   The detective is seen in the movie, and\n",
      "he is trying to solve the case. *   The detective is seen in the movie, and he\n",
      "is trying to catch the thief. *   The detective is seen in the movie, and he is\n",
      "trying to save the girl. *   The detective is seen in the movie, and he is\n",
      "reunited with the girl. *   The movie ends.  **Main Character and Problems:**  *\n",
      "**Main Character:** The young man working as a movie projectionist (played by\n",
      "Buster Keaton). He is also studying to be a detective. *   **Problems:**     *\n",
      "He is trying to balance his job with his dream of becoming a detective.     *\n",
      "He is not taken seriously as a detective.     *   He is accused of stealing a\n",
      "watch.     *   He is kicked out of the girl's house.     *   He is trying to\n",
      "save the girl from the thief.\n",
      "Time elapsed: 40.79677771499996 seconds\n"
     ]
    }
   ],
   "source": [
    "# Time the call without context caching\n",
    "from timeit import default_timer as timer\n",
    "\n",
    "start = timer()\n",
    "generate(gemini, contents)\n",
    "end = timer()\n",
    "\n",
    "print(f\"\\nTime elapsed: {end - start} seconds\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "id": "K27kb9ofVD-L",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import datetime\n",
    "\n",
    "from vertexai.preview import caching\n",
    "from vertexai.preview.generative_models import GenerativeModel\n",
    "\n",
    "cached_content = caching.CachedContent.create(\n",
    "    model_name=\"gemini-2.0-flash-001\",\n",
    "    contents=[long_video_content],\n",
    "    ttl=datetime.timedelta(hours=1),\n",
    "    display_name=\"long video cache\",\n",
    ")\n",
    "\n",
    "model_cached = GenerativeModel.from_cached_content(cached_content=cached_content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "id": "8sf4bx6NOSP2",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here's a breakdown of the video:  **Beginning (0:00-1:25):**  *   The video\n",
      "starts with the title card for the film \"Sherlock Jr.\" starring Buster Keaton,\n",
      "presented by Joseph M. Schenck. *   Credits are shown, including director,\n",
      "writers, photography, art director, and electrician. *   Copyright information\n",
      "is displayed, indicating the film was copyrighted in 1924. *   A proverb is\n",
      "presented: \"Don't try to do two things at once and expect to do justice to\n",
      "both.\" *   The introduction explains that the story is about a boy who tried to\n",
      "do two things at once: work as a moving picture operator and study to be a\n",
      "detective.  **Middle (1:26-38:59):**  *   The scene shifts to a movie theater\n",
      "where a young man (Buster Keaton) is reading a book titled \"How-To-Be-A-\n",
      "Detective.\" *   His boss tells him to clean the theater instead of reading. *\n",
      "The young man is shown working at the theater, but he is distracted by his\n",
      "detective studies. *   He tries to buy a box of chocolates for a girl he likes,\n",
      "but he doesn't have enough money. *   The girl is seen with another man, who\n",
      "buys her a more expensive box of chocolates. *   The young man is called to a\n",
      "house where a crime has been committed. *   He tries to investigate, but he is\n",
      "clumsy and makes mistakes. *   He is accused of stealing a watch and is kicked\n",
      "out of the house. *   He returns to the theater and falls asleep in the\n",
      "projection booth. *   While asleep, he dreams that he enters the movie screen\n",
      "and becomes the detective in the film. *   He interacts with the characters and\n",
      "tries to solve the crime, but he is clumsy and makes mistakes. *   He is chased\n",
      "by the villains and ends up in a series of dangerous situations. *   He is\n",
      "chased by the police and ends up falling into a river.  **End (38:59-44:06):**\n",
      "*   The young man wakes up from his dream and realizes that he is late for work.\n",
      "*   He rushes to the projection booth and starts the movie. *   He sees the girl\n",
      "he likes in the audience and realizes that she is in danger. *   He enters the\n",
      "movie screen and saves her from the villains. *   The film ends with the young\n",
      "man and the girl together.  **Main Character and Problems:**  *   **Main\n",
      "Character:** The young man, played by Buster Keaton, who works as a movie\n",
      "projectionist and aspires to be a detective. *   **Problems:**     *   Balancing\n",
      "his job with his detective studies.     *   Not having enough money to impress\n",
      "the girl he likes.     *   Being accused of stealing a watch.     *   Being\n",
      "clumsy and making mistakes as a detective.     *   Being unable to save the girl\n",
      "he likes in the real world.\n",
      "Time elapsed: 29.962379157999976 seconds\n"
     ]
    }
   ],
   "source": [
    "# Call with context caching\n",
    "start = timer()\n",
    "responses = model_cached.generate_content(\n",
    "    prompt,\n",
    "    generation_config=GENERATION_CONFIG,\n",
    "    safety_settings=SAFETY_CONFIG,\n",
    "    stream=False,\n",
    ")\n",
    "end = timer()\n",
    "\n",
    "print(wrap(responses.text), end=\"\")\n",
    "\n",
    "print(f\"\\nTime elapsed: {end - start} seconds\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zO4-G73j6waM"
   },
   "source": [
    "As we see the result with context caching was relatively faster than without context caching. Not only that, the cost of the request is lower as we did not need to send the video again during the prompt for analysis.\n",
    "\n",
    "Context caching therefore is ideal for the repeated questions against the same long file: video, document, audio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wSNrNDh2Ev0G"
   },
   "source": [
    "# Conclusion\n",
    "\n",
    "This demonstrated various examples of working with Gemini using videos. Following are general prompting strategies when working with Gemini on multimodal prompts, that can help achieve better performance from Gemini:\n",
    "\n",
    "1. Craft clear and concise instructions.\n",
    "1. Add your video or any media first for single-media prompts.\n",
    "1. Add few-shot examples to the prompt to show the model how you want the task done and the expected output.\n",
    "1. Break down the task step-by-step.\n",
    "1. Specify the output format.\n",
    "1. Ask Gemini to include reasoning in its response along with decision or scores\n",
    "1. Use context caching for repeated queries.\n",
    "\n",
    "Specifically, when working with videos following may help:\n",
    "\n",
    "1. Specify timestamp format when localizing videos.\n",
    "1. Ask Gemini to focus on visual content for well-known video clips.\n",
    "1. Process long videos in segments for dense outputs.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ebce3772f858"
   },
   "source": [
    "---"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "multimodal_prompting_video.ipynb",
   "toc_visible": true
  },
  "environment": {
   "kernel": "conda-base-py",
   "name": "workbench-notebooks.m128",
   "type": "gcloud",
   "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m128"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "conda-base-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
